﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Text;


using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Executes a command which redirects a client to a parametrized URL.
    /// </summary>
    /// <remarks><b>RedirectCommand</b> doesn't support multiple records, ie. the <b>ICommand.Parameters.Count</b>,
    /// <see cref="ICommand.MinParameterSetSize"/> and <see cref="ICommand.MaxParameterSetSize"/> properties may 
    /// not be greater than 1.</remarks>
    public class RedirectCommand : WebCommandBase
    {
        #region Fields.

        string urlFormat;
        string currentUrlQueryKey;
        Delegates.Function<string> currentUrlProvider;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <b>RedirectCommand</b> class.
        /// </summary>
        protected RedirectCommand()
        { }

        /// <summary>
        /// Initializes a new instance of the <b>RedirectCommand</b> class.
        /// </summary>
        /// <param name="key">Command key. Nullable. Should be unique in the collection and language neutral.</param>
        /// <param name="text">Command description.</param>
        /// <param name="url">URL. E.g. "http://www.google.com".</param>
        /// <remarks>Use this constructor overload if the command doesn't require parameters.</remarks>
        public RedirectCommand(string key, string text, string url)
            : base(key, text)
        {
            SetUrlFormat(url);
        }

        /// <summary>
        /// Initializes a new instance of the <b>RedirectCommand</b> class.
        /// </summary>
        /// <param name="key">Command key. Nullable. Should be unique in the collection and language neutral.</param>
        /// <param name="text">Command description.</param>
        /// <param name="urlFormat">URL format. E.g. "WebForm1.aspx?id={0}".</param>
        /// <param name="requiredParameters">Array of parameters whose values are used to fill the callFormat.
        /// Must be ordered in the same fashion as the <b>urlFormat</b> parameters.</param>
        public RedirectCommand(string key, string text, string urlFormat, string[] requiredParameters)
            : base(key, text, requiredParameters)
        {
            SetUrlFormat(urlFormat);
        }

        private void SetUrlFormat(string relativeUrlFormat)
        {
            this.urlFormat = WebUtil.ResolveUrl(relativeUrlFormat);
        }

        #endregion

        #region RenderUrl.

        /// <summary>
        /// Renders URL to which a client will be redirected.
        /// </summary>
        /// <returns>A string representing a valid URL.</returns>
        /// <remarks>You can override this method to perform additional URL processing before the command is executed.</remarks>
        protected virtual string RenderUrl()
        {
            EnsureParameters(true);
            // User can select as many records as they wish only when redirect command is not context sensitive, ie. requires no params.
            // For these commands RenderUrl is invoked in "rendering" phase to optimize performance - no round trip to server is required to redirect.
            if ((this.RequiredParameters.Length > 0) && (this.Parameters.Count > 1))
                throw new Exception("Multiple records not supported.");

            string[] queryStringParameters = new string[this.RequiredParameters.Length];
            for (int idxPar = 0; idxPar < queryStringParameters.Length; idxPar++)
            {
                object parValue = GetParameterValue(0, this.RequiredParameters[idxPar]);
                string parText = (this.ParameterFormatter != null) ? this.ParameterFormatter.Format(parValue) : Convert.ToString(parValue);
                parText = parText.Replace(@"\", @"\\");
                parText = parText.Replace(@"'", @"\'");
                parText = parText.Replace("\"", "\\\"");

                queryStringParameters[idxPar] = parText;
            }

            string url = (queryStringParameters.Length == 0) ? this.urlFormat : string.Format(this.urlFormat, queryStringParameters);
            if (!string.IsNullOrEmpty(this.currentUrlQueryKey))
                url = WebUtil.SetUrlParam(url, this.currentUrlQueryKey, GetCurrentUrl());

            return url;
        }

        #endregion

        #region Execute.

        private static Page GetPage()
        {
            return HttpContext.Current.Handler as Page;
        }

        private string GetCurrentUrl()
        {
            if (this.currentUrlProvider != null)
                return this.currentUrlProvider();
            else
                return GetPage().Request.RawUrl;
        }

        /// <summary>
        /// Executes the command with provided parameters.
        /// </summary>
        /// <param name="caller">Object/client which is calling the method.</param>
        /// <param name="actor">Command initiator. This may be an application user, software module, 3rd party 
        /// application etc. which is initiating the command.</param>
        /// <remarks><para>This overload should be used when the command should perform additional security
        /// checks (vertical/horizontal) or when the command execution must be logged. Preffer using
        /// this overload whenever the <see cref="IActor"/> object is available.</para></remarks>
        public override void Execute(object caller, IActor actor)
        {
            string url = RenderUrl();
            HttpContext.Current.Response.Redirect(url);
        }

        #endregion

        #region ExecuteOnClient.

        /// <summary>
        /// Gets the script generated by the command which starts it's execution in the Web browser.
        /// </summary>
        /// <remarks>Commands which don't require parameters will be entirely executed in the Web browser
        /// and will not invoke server side method.</remarks>
        public override string ExecuteOnClient
        {
            get
            {
                string script = "";
                if (!string.IsNullOrEmpty(this.PrependToExecuteOnClient))
                    script += this.PrependToExecuteOnClient;

                if (this.RequiredParameters.Length == 0)
                {
                    string url = RenderUrl();
                    script += "document.location=\'" + url + "'; ";
                }
                else
                {
                    script += this.InvokeServerSideExecution;
                }

                if (!string.IsNullOrEmpty(this.AppendToExecuteOnClient))
                    script += this.AppendToExecuteOnClient;

                return script;
            }
        }

        #endregion

        #region Properties and setters.

        /// <summary>
        /// Sets the optional query key by which a current URL is sent to new page.
        /// </summary>
        /// <remarks>Usefull when you programmatically want to return to current URL.</remarks>
        public string CurrentUrlQueryKey
        {
            set { this.currentUrlQueryKey = value; }
        }

        /// <summary>
        /// Sets the method which provides values for the <see cref="CurrentUrlQueryKey"/> query string variable.
        /// </summary>
        /// <param name="method">A method which returns a <see cref="System.String"/>. May be null.</param>
        /// <remarks>Usefull when additional processing of the current URL is required to modify it. If null, 
        /// <see cref="HttpRequest.RawUrl"/> is used. Has no affect if <see cref="CurrentUrlQueryKey"/> is not set.</remarks>
        public void SetCurrentUrlProvider(Delegates.Function<string> method)
        {
            this.currentUrlProvider = method;
        }

        #endregion

        #region Clone, CopyFrom.

        /// <summary>
        /// Creates a new object that is a copy of the current instance. 
        /// </summary>
        public override object Clone()
        {
            if (GetType() != typeof(RedirectCommand))
                throw new InvalidOperationException("Clone method must be overriden in derived classes. Do not use \"base.Clone()\" in derived class.");

            RedirectCommand clone = new RedirectCommand();
            clone.CopyFrom(this);
            return clone;
        }

        /// <summary>
        /// Copies information from an <b>RedirectCommand</b> object. 
        /// </summary>
        /// <param name="source">The <b>RedirectCommand</b> object to base the new object on.</param>
        /// <remarks>This method copies information to an RedirectCommand object contained in the specified 
        /// RedirectCommand object. Override in derived classes if new class fields/members are introduced.</remarks>
        protected virtual void CopyFrom(RedirectCommand source)
        {
            base.CopyFrom(source);
            this.urlFormat = source.urlFormat;
            this.currentUrlQueryKey = source.currentUrlQueryKey;
            this.currentUrlProvider = source.currentUrlProvider;
        }

        #endregion
    }
}